/*
 * Copyright (c) 2008 VMware, Inc.
 * Copyright (c) 2009 John Pritchard, WTKX Project Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wtkx.in;


import wtkx.Component;
import wtkx.Scene;
import wtkx.ui.Direction;
import wtkx.ui.DragSource;
import wtkx.ui.DropAction;
import wtkx.ui.DropTarget;
import wtkx.ui.Keyboard;
import wtkx.ui.Manifest;
import wtkx.ui.Mouse;
import wtkx.ui.Platform;

import java.awt.AWTEvent;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Point;
import java.awt.Transparency;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSourceContext;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.Point2D;

/**
 * Code shared by {@link Frame} and {@link Applet} for AWT events
 * processing and application repainting.
 * 
 * @see Applet
 * @see Frame
 * @see Canvas
 * @author gbrown
 * @author jdp
 */
public final class Awtk
    implements DropTargetListener
{
    public static Component getDropDescendant(Scene scene, int x, int y) {

        Component dropDescendant = scene.getDescendantAt(x, y);

        while (dropDescendant != null
               && dropDescendant.getDropTarget() == null) {
            dropDescendant = dropDescendant.getParent();
        }

        if (dropDescendant != null
            && dropDescendant.isEnabledAncestry()) {
            dropDescendant = null;
        }

        return dropDescendant;
    }

    public static DropAction getUserDropAction(InputEvent event) {
        DropAction userDropAction;

        if ((event.isControlDown() && event.isShiftDown())
            || (event.isAltDown() && event.isMetaDown())) {
            userDropAction = DropAction.LINK;
        } else if (event.isControlDown()
            || (event.isAltDown())) {
            userDropAction = DropAction.COPY;
        } else if (event.isShiftDown()){
            userDropAction = DropAction.MOVE;
        } else {
            userDropAction = null;
        }

        return userDropAction;
    }

    public static DropAction getDropAction(int nativeDropAction) {
        DropAction dropAction = null;

        switch (nativeDropAction) {
            case DnDConstants.ACTION_COPY: {
                dropAction = DropAction.COPY;
                break;
            }

            case DnDConstants.ACTION_MOVE: {
                dropAction = DropAction.MOVE;
                break;
            }

            case DnDConstants.ACTION_LINK: {
                dropAction = DropAction.LINK;
                break;
            }
        }

        return dropAction;
    }

    public static int getSupportedDropActions(int sourceActions) {
        int dropActions = 0;

        if ((sourceActions & DnDConstants.ACTION_COPY) > 0) {
            dropActions |= DropAction.COPY.getMask();
        }

        if ((sourceActions & DnDConstants.ACTION_MOVE) > 0) {
            dropActions |= DropAction.MOVE.getMask();
        }

        if ((sourceActions & DnDConstants.ACTION_LINK) > 0) {
            dropActions |= DropAction.LINK.getMask();
        }

        return dropActions;
    }

    public static int getNativeDropAction(DropAction dropAction) {
        int nativeDropAction = 0;

        if (dropAction != null) {
            switch(dropAction) {
                case COPY: {
                    nativeDropAction = DnDConstants.ACTION_COPY;
                    break;
                }

                case MOVE: {
                    nativeDropAction = DnDConstants.ACTION_MOVE;
                    break;
                }

                case LINK: {
                    nativeDropAction = DnDConstants.ACTION_LINK;
                    break;
                }
            }
        }

        return nativeDropAction;
    }

    public static java.awt.Cursor getDropCursor(DropAction dropAction) {
        // Update the drop cursor
        java.awt.Cursor cursor = java.awt.Cursor.getDefaultCursor();

        if (dropAction != null) {
            // Show the cursor for the drop action returned by the
            // drop target
            switch (dropAction) {
                case COPY: {
                    cursor = java.awt.dnd.DragSource.DefaultCopyDrop;
                    break;
                }

                case MOVE: {
                    cursor = java.awt.dnd.DragSource.DefaultMoveDrop;
                    break;
                }

                case LINK: {
                    cursor = java.awt.dnd.DragSource.DefaultLinkDrop;
                    break;
                }
            }
        }

        return cursor;
    }

    public static class DSL 
        extends Object
        implements DragSourceListener
    {
        public DSL(){
            super();
        }
        public void dragEnter(DragSourceDragEvent event) {
            DragSourceContext context = event.getDragSourceContext();
            context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
        }

        public void dragExit(DragSourceEvent event) {
            DragSourceContext context = event.getDragSourceContext();
            context.setCursor(java.awt.Cursor.getDefaultCursor());
        }

        public void dragOver(DragSourceDragEvent event) {
            DragSourceContext context = event.getDragSourceContext();
            context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
        }

        public void dropActionChanged(DragSourceDragEvent event) {
            DragSourceContext context = event.getDragSourceContext();
            context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
        }

        public void dragDropEnd(DragSourceDropEvent event) {
            DragSourceContext context = event.getDragSourceContext();
            context.setCursor(java.awt.Cursor.getDefaultCursor());
        }
    }

    public static class DGR
        extends DragGestureRecognizer
    {
        private static final long serialVersionUID = 0;


        private final int awtSourceActions;


        public DGR(Awtk li,DragSource src,  MouseEvent mouseEvent){
            super(java.awt.dnd.DragSource.getDefaultDragSource(),li.component);

            int supportedDropActions = src.getSupportedDropActions();

            int awtSourceActions = 0;
            if (DropAction.COPY.isSelected(supportedDropActions)) {
                awtSourceActions |= DnDConstants.ACTION_COPY;
            }
            if (DropAction.MOVE.isSelected(supportedDropActions)) {
                awtSourceActions |= DnDConstants.ACTION_MOVE;
            }
            if (DropAction.LINK.isSelected(supportedDropActions)) {
                awtSourceActions |= DnDConstants.ACTION_LINK;
            }
            this.awtSourceActions = awtSourceActions;

            this.appendEvent(mouseEvent);
        }

        public int getSourceActions() {
            return awtSourceActions;
        }
        protected void registerListeners() {
        }
        protected void unregisterListeners() {
        }
    }



    private final java.awt.Container container;

    private final java.awt.Component component;

    private final Scene scene;

    private Screen screen;

    private BackingStore graphics;

    private boolean reqrep;

    private Animator animator;


    private Point mouseLocation;

    private Point2D.Float dragLocation;
    private Component dragDescendant;

    private Manifest dragManifest;

    private DropAction userDropAction;
    private Component dropDescendant;

    private DropTargetListener dropTargetListener;


    public Awtk(java.awt.Container container, BackingStore.Component component, Scene scene){
        this(container,((java.awt.Component)component),scene);
    }
    public Awtk(java.awt.Container container, java.awt.Component component, Scene scene){
        super();
        if (null != container && null != component && null != scene){
            this.container = container;
            this.component = component;
            this.scene = scene;

            new java.awt.dnd.DropTarget(component, this);
            try {
                System.setProperty("sun.awt.noerasebackground", "true");
                System.setProperty("sun.awt.erasebackgroundonresize", "false");
            }
            catch (java.security.AccessControlException ignore){
            }
        }
        else
            throw new IllegalArgumentException();
    }


    public Scene getScene(){
        return this.scene;
    }
    public Screen getScreen(){
        return this.screen;
    }
    public java.awt.Robot getRobot()
        throws java.awt.AWTException
    {
        Screen screen = this.screen;
        if (null != screen)
            return screen.getRobot();
        else
            return null;
    }
    public BackingStore getBacking(){
        return this.graphics;
    }
    public synchronized void init(Screen screen, BackingStore graphics){

        this.screen = screen;
        this.graphics = graphics;
    }
    public synchronized void destroy(){

        if (null != this.animator){
            if (Thread.State.TERMINATED != this.animator.getState()){
                this.animator.stop();
                Thread.yield();
            }
        }
        this.screen = null;
        this.graphics = null;
        this.animator = null;
    }
    public synchronized void start(){

        if (null != this.animator){
            if (graphics == this.graphics){
                if (Thread.State.TERMINATED != this.animator.getState())
                    return;
            }
            else if (Thread.State.TERMINATED != this.animator.getState()){
                this.animator.stop();
            }
        }
        this.animator = new Animator(this);
        this.animator.start();
        if (this.reqrep){
            this.reqrep = false;
            this.animator.repaint(this.component);
        }
    }
    public synchronized void stop(){

        if (null != this.animator){
            if (Thread.State.TERMINATED != this.animator.getState()){
                this.animator.stop();
            }
        }
        this.animator = null;

        wtkx.ui.Threads.Shutdown();
    }
    public void sync(){
        this.component.getToolkit().sync();
    }
    public boolean idle() {
        this.sync();
        System.gc();
        System.runFinalization();
        System.gc();
        this.sync();
        try {
            Thread.sleep(50);

            return false;
        }
        catch (InterruptedException e) {
            return true;
        }
    }
    public void repaint(){
        Animator animator = this.animator;
        if (null != animator)
            animator.repaint(this.component);
        else 
            this.reqrep = true;//(not yet initialized)
    }
    public void repaint(int x, int y, int w, int h){
        Animator animator = this.animator;
        if (null != animator){
            if (0 < w && 0 < h)
                animator.repaint(x,y,w,h);
            else 
                animator.repaint(this.component);
        }
        else
            this.reqrep = true;//(not yet initialized)
    }
    public Point getMouseLocation() {
        return mouseLocation;
    }
    public void paint(Graphics2D g2) {

        if (dragDescendant != null) {

            DragSource dragSource = dragDescendant.getDragSource();

            Component dragRepresentation = dragSource.getRepresentation();

            if (dragRepresentation != null) {
                Point dragOffset = dragSource.getOffset();
                int tx = (int)(dragLocation.x - dragOffset.x);
                int ty = (int)(dragLocation.y - dragOffset.y);

                g2.translate(tx, ty);

                dragRepresentation.paint(g2);
            }
        }
    }
    public void dragEnter(DropTargetDragEvent event) {
        if (dragDescendant != null) {
            throw new IllegalStateException("Local drag already in progress.");
        }

        java.awt.Point location = event.getLocation();
        mouseLocation = new Point(location.x, location.y);

        // Initialize drag state
        dragManifest = new RemoteManifest(event.getTransferable());

        // Initialize drop state
        userDropAction = getDropAction(event.getDropAction());

        // Notify drop target
        dropDescendant = getDropDescendant(this.scene, location.x, location.y);

        DropAction dropAction = null;

        if (dropDescendant != null) {
            DropTarget dropTarget = dropDescendant.getDropTarget();
            dropAction = dropTarget.dragEnter(dropDescendant, dragManifest,
                                              getSupportedDropActions(event.getSourceActions()),
                                              userDropAction);
        }

        if (dropAction == null) {
            event.rejectDrag();
        } else {
            event.acceptDrag(getNativeDropAction(dropAction));
        }
    }

    public void dragExit(DropTargetEvent event) {
        // Clear mouse location
        mouseLocation = null;

        // Clear drag state
        dragManifest = null;

        // Clear drop state
        userDropAction = null;

        if (dropDescendant != null) {
            DropTarget dropTarget = dropDescendant.getDropTarget();
            dropTarget.dragExit(dropDescendant);
        }

        dropDescendant = null;
    }

    public void dragOver(DropTargetDragEvent event) {
        java.awt.Point location = event.getLocation();

        // Get the previous and current drop descendant and call
        // move or exit/enter as appropriate
        Component previousDropDescendant = dropDescendant;
        dropDescendant = getDropDescendant(this.scene, location.x, location.y);

        DropAction dropAction = null;

        if (previousDropDescendant == dropDescendant) {
            if (dropDescendant != null) {
                DropTarget dropTarget = dropDescendant.getDropTarget();

                Scene scene = this.scene;

                Point2D.Float dropLocation = dropDescendant.mapPointFromAncestor(scene,
                                                                                 location.x, location.y);
                dropAction = dropTarget.dragMove(dropDescendant, dragManifest,
                                                 getSupportedDropActions(event.getSourceActions()),
                                                 (int)dropLocation.x, (int)dropLocation.y, userDropAction);
            }
        } else {
            if (previousDropDescendant != null) {
                DropTarget previousDropTarget = previousDropDescendant.getDropTarget();
                previousDropTarget.dragExit(previousDropDescendant);
            }

            if (dropDescendant != null) {
                DropTarget dropTarget = dropDescendant.getDropTarget();
                dropAction = dropTarget.dragEnter(dropDescendant, dragManifest,
                                                  getSupportedDropActions(event.getSourceActions()),
                                                  userDropAction);
            }
        }

        // Update cursor
        this.component.setCursor(getDropCursor(dropAction));

        if (dropAction == null) {
            event.rejectDrag();
        } else {
            event.acceptDrag(getNativeDropAction(dropAction));
        }
    }

    public void dropActionChanged(DropTargetDragEvent event) {
        userDropAction = getDropAction(event.getDropAction());

        DropAction dropAction = null;

        if (dropDescendant != null) {
            java.awt.Point location = event.getLocation();

            Scene scene = this.scene;

            Point2D.Float dropLocation = dropDescendant.mapPointFromAncestor(scene,
                                                                             location.x, location.y);

            DropTarget dropTarget = dropDescendant.getDropTarget();
            dropAction = dropTarget.userDropActionChange(dropDescendant, dragManifest,
                                                         getSupportedDropActions(event.getSourceActions()),
                                                         (int)dropLocation.x, (int)dropLocation.y,
                                                         userDropAction);
        }

        if (dropAction == null) {
            event.rejectDrag();
        } else {
            event.acceptDrag(getNativeDropAction(dropAction));
        }
    }

    public void drop(DropTargetDropEvent event) {
        java.awt.Point location = event.getLocation();
        dropDescendant = getDropDescendant(this.scene, location.x, location.y);

        DropAction dropAction = null;

        if (dropDescendant != null) {

            Scene scene = this.scene;

            Point2D.Float dropLocation = dropDescendant.mapPointFromAncestor(scene,
                                                                             location.x, location.y);
            DropTarget dropTarget = dropDescendant.getDropTarget();

            // Simulate a user drop action change to get the current drop action
            int supportedDropActions = getSupportedDropActions(event.getSourceActions());

            dropAction = dropTarget.userDropActionChange(dropDescendant, dragManifest,
                                                         supportedDropActions, 
                                                         (int)dropLocation.x, (int)dropLocation.y, 
                                                         userDropAction);

            if (dropAction != null) {
                // Perform the drop
                event.acceptDrop(getNativeDropAction(dropAction));
                dropTarget.drop(dropDescendant, dragManifest,
                                supportedDropActions, dropLocation.x, dropLocation.y, userDropAction);
            }
        }

        if (dropAction == null) {
            event.rejectDrop();
        }

        event.dropComplete(true);

        // Restore the cursor to the default
        this.component.setCursor(java.awt.Cursor.getDefaultCursor());

        // Clear drag state
        dragManifest = null;

        // Clear drop state
        dropDescendant = null;
    }

    private void repaintDragRepresentation() {
        DragSource dragSource = dragDescendant.getDragSource();
        Component dragRepresentation = dragSource.getRepresentation();

        if (dragRepresentation != null) {
            Point dragOffset = dragSource.getOffset();

            int x = (int)(dragLocation.x - dragOffset.x);
            int y = (int)(dragLocation.y - dragOffset.y);
            int w = (int)dragRepresentation.getWidth();
            int h = (int)dragRepresentation.getHeight();
            this.component.repaint(x, y, w, h);
        }
    }

    private void startNativeDrag(DragSource dragSource, MouseEvent mouseEvent) {
        java.awt.dnd.DragSource awtDragSource = java.awt.dnd.DragSource.getDefaultDragSource();

        DragGestureRecognizer dragGestureRecognizer = new DGR(this,dragSource,mouseEvent);

        java.util.List<InputEvent> inputEvents = new java.util.ArrayList<InputEvent>();
        inputEvents.add(mouseEvent);

        /* 
         * [TODO] If current user drop action is supported by drag
         *        source, use it as initial action - otherwise, select
         *        MOVE, COPY, LINK in that order
         */
        java.awt.Point location = new java.awt.Point(mouseEvent.getX(), mouseEvent.getY());
        DragGestureEvent trigger = new DragGestureEvent(dragGestureRecognizer,
                                                        DnDConstants.ACTION_MOVE, location, inputEvents);

        LocalManifest dragContent = dragSource.getContent();
        LocalManifestAdapter localManifestAdapter = new LocalManifestAdapter(dragContent);

        awtDragSource.startDrag(trigger, java.awt.Cursor.getDefaultCursor(),
                                null, null, localManifestAdapter, new Awtk.DSL());
    }

    protected void processComponentEvent(ComponentEvent event) {
        //super.processComponentEvent(event);//

        switch (event.getID()) {
        case ComponentEvent.COMPONENT_RESIZED: {
            this.scene.setSize(this.component.getWidth(), this.component.getHeight());
            break;
        }

        case ComponentEvent.COMPONENT_MOVED: {
            // No-op
            break;
        }

        case ComponentEvent.COMPONENT_SHOWN: {
            // No-op
            break;
        }

        case ComponentEvent.COMPONENT_HIDDEN: {
            // No-op
            break;
        }
        }
    }

    protected void processFocusEvent(FocusEvent event) {
        //super.processFocusEvent(event);//

        Component focusedComponent = Component.GetFocusedComponent();

        switch(event.getID()) {
        case FocusEvent.FOCUS_GAINED:

            if (null != focusedComponent)
                focusedComponent.focusChanged(true);
            else
                this.scene.transferFocus(Direction.Forward);
                
            break;

        case FocusEvent.FOCUS_LOST:

            if (null != focusedComponent)
                focusedComponent.focusChanged(false);

            break;
        }
    }

    protected void processMouseEvent(MouseEvent event) {
        //super.processMouseEvent(event);//

        Scene scene = this.scene;

        int x = event.getX();
        int y = event.getY();

        // Set the mouse button state
        int mouseButtons = 0x00;

        int modifiersEx = event.getModifiersEx();
        if ((modifiersEx & MouseEvent.BUTTON1_DOWN_MASK) > 0) {
            mouseButtons |= Mouse.Button.LEFT.getMask();
        }

        if ((modifiersEx & MouseEvent.BUTTON2_DOWN_MASK) > 0) {
            mouseButtons |= Mouse.Button.MIDDLE.getMask();
        }

        if ((modifiersEx & MouseEvent.BUTTON3_DOWN_MASK) > 0) {
            mouseButtons |= Mouse.Button.RIGHT.getMask();
        }

        Mouse.setButtons(mouseButtons);

        // Get the button associated with this event
        Mouse.Button button = null;
        switch (event.getButton()) {
        case MouseEvent.BUTTON1: {
            button = Mouse.Button.LEFT;
            break;
        }

        case MouseEvent.BUTTON2: {
            button = Mouse.Button.MIDDLE;
            break;
        }

        case MouseEvent.BUTTON3: {
            button = Mouse.Button.RIGHT;
            break;
        }
        }

        // Process the event
        int eventID = event.getID();
        if (eventID == MouseEvent.MOUSE_ENTERED
            || eventID == MouseEvent.MOUSE_EXITED) {
            switch(eventID) {
            case MouseEvent.MOUSE_ENTERED: {
                mouseLocation = new Point(x, y);
                scene.mouseOver();
                break;
            }

            case MouseEvent.MOUSE_EXITED: {
                scene.mouseOut();
                mouseLocation = null;
                break;
            }
            }
        } else {
            // Determine the mouse owner
            Component mouseOwner;
            Component mouseCapturer = Mouse.getCapturer();
            if (mouseCapturer == null) {
                mouseOwner = scene;
            }
            else {
                mouseOwner = mouseCapturer;
                Point2D.Float location = mouseOwner.mapPointFromAncestor(scene, x, y);
                x = (int)location.x;
                y = (int)location.y;
            }

            // Delegate the event to the owner
            switch (eventID) {
            case MouseEvent.MOUSE_PRESSED: {
                this.component.requestFocus();
                dragLocation = new Point2D.Float(x, y);
                mouseOwner.mouseDown(button, x, y);
                break;
            }

            case MouseEvent.MOUSE_RELEASED: {
                if (dragDescendant == null) {
                    mouseOwner.mouseUp(button, x, y);
                } else {
                    DragSource dragSource = dragDescendant.getDragSource();

                    repaintDragRepresentation();

                    if (dropDescendant == null) {
                        dragSource.endDrag(dragDescendant, null);
                    } else {
                        DropTarget dropTarget = dropDescendant.getDropTarget();
                        DropAction dropAction = dropTarget.drop(dropDescendant, dragManifest,
                                                                dragSource.getSupportedDropActions(), x, y, 
                                                                Awtk.getUserDropAction(event));
                        dragSource.endDrag(dragDescendant, dropAction);
                    }

                    this.component.setCursor(java.awt.Cursor.getDefaultCursor());

                    // Clear the drag state
                    dragDescendant = null;
                    dragManifest = null;

                    // Clear the drop state
                    userDropAction = null;
                    dropDescendant = null;
                }

                // Clear the drag location
                dragLocation = null;

                break;
            }
            }
        }
    }

    protected void processMouseMotionEvent(MouseEvent event) {
        //super.processMouseMotionEvent(event);//

        Scene scene = this.scene;

        int x = event.getX();
        int y = event.getY();

        // Set the mouse location
        if (mouseLocation == null) {
            mouseLocation = new Point();
        }

        mouseLocation.x = x;
        mouseLocation.y = y;

        // Process the event
        switch (event.getID()) {
        case MouseEvent.MOUSE_MOVED:
        case MouseEvent.MOUSE_DRAGGED: {
            if (dragDescendant == null) {
                // A drag is not active
                Component mouseCapturer = Mouse.getCapturer();

                if (mouseCapturer == null) {
                    // The mouse is not captured, so propagate the event to the scene
                    scene.mouseMove(x, y);

                    int dragThreshold = Platform.getDragThreshold();

                    if (dragLocation != null
                        && (Math.abs(x - dragLocation.x) > dragThreshold
                            || Math.abs(y - dragLocation.y) > dragThreshold)) {
                        // The user has dragged the mouse past the drag threshold; try
                        // to find a drag source
                        dragDescendant = scene.getDescendantAt(dragLocation.x,
                                                                 dragLocation.y);

                        while (dragDescendant != null
                               && dragDescendant.getDragSource() == null) {
                            dragDescendant = dragDescendant.getParent();
                        }

                        if (dragDescendant == null
                            || dragDescendant.isEnabledAncestry()) {
                            // There was nothing to drag, so clear the drag location
                            dragDescendant = null;
                            dragLocation = null;
                        } else {
                            DragSource dragSource = dragDescendant.getDragSource();
                            dragLocation = dragDescendant.mapPointFromAncestor(scene, x, y);

                            if (dragSource.beginDrag(dragDescendant, dragLocation.x, dragLocation.y)) {
                                // A drag has started
                                if (dragSource.isNative()) {
                                    // Clear the drag state since it is not used for
                                    // native drags
                                    dragDescendant = null;
                                    dragLocation = null;

                                    startNativeDrag(dragSource, event);
                                } else {
                                    if (dragSource.getRepresentation() != null
                                        && dragSource.getOffset() == null) {
                                        throw new IllegalStateException("Drag offset is required when a "
                                                                        + " respresentation is specified.");
                                    }

                                    if (scene.isMouseOver()) {
                                        scene.mouseOut();
                                    }

                                    // Get the drag content
                                    dragManifest = dragSource.getContent();

                                    // Get the initial user drop action
                                    userDropAction = Awtk.getUserDropAction(event);

                                    // Repaint the drag visual
                                    dragLocation.x = x;
                                    dragLocation.y = y;

                                    repaintDragRepresentation();
                                }
                            } else {
                                // Clear the drag state
                                dragDescendant = null;
                                dragLocation = null;
                            }
                        }
                    }
                } else {
                    // Delegate the event to the capturer
                    Point2D.Float location = mouseCapturer.mapPointFromAncestor(scene, x, y);
                    mouseCapturer.mouseMove( (int)location.x, (int)location.y);
                }
            } else {
                if (dragLocation != null) {
                    DragSource dragSource = dragDescendant.getDragSource();

                    // Get the previous and current drop descendant and call
                    // move or exit/enter as appropriate
                    Component previousDropDescendant = dropDescendant;
                    dropDescendant = Awtk.getDropDescendant(this.scene, x, y);

                    DropAction dropAction = null;

                    if (previousDropDescendant == dropDescendant) {
                        if (dropDescendant != null) {
                            DropTarget dropTarget = dropDescendant.getDropTarget();

                            Point2D.Float dropLocation = dropDescendant.mapPointFromAncestor(scene, x, y);
                            dropAction = dropTarget.dragMove(dropDescendant, dragManifest,
                                                             dragSource.getSupportedDropActions(),
                                                             (int)dropLocation.x, (int)dropLocation.y,
                                                             userDropAction);
                        }
                    } else {
                        if (previousDropDescendant != null) {
                            DropTarget previousDropTarget = previousDropDescendant.getDropTarget();
                            previousDropTarget.dragExit(previousDropDescendant);
                        }

                        if (dropDescendant != null) {
                            DropTarget dropTarget = dropDescendant.getDropTarget();
                            dropAction = dropTarget.dragEnter(dropDescendant, dragManifest,
                                                              dragSource.getSupportedDropActions(),
                                                              userDropAction);
                        }
                    }

                    // Update cursor
                    this.component.setCursor(Awtk.getDropCursor(dropAction));

                    // Repaint the drag visual
                    repaintDragRepresentation();

                    dragLocation.x = x;
                    dragLocation.y = y;

                    repaintDragRepresentation();
                }
            }

            break;
        }
        }
    }

    protected void processMouseWheelEvent(MouseWheelEvent event) {
        //super.processMouseWheelEvent(event);//

        Scene scene = this.scene;

        // Get the event coordinates
        int x = event.getX();
        int y = event.getY();

        // Get the scroll type
        Mouse.ScrollType scrollType = null;
        switch (event.getScrollType()) {
        case MouseWheelEvent.WHEEL_BLOCK_SCROLL: {
            scrollType = Mouse.ScrollType.BLOCK;
            break;
        }

        case MouseWheelEvent.WHEEL_UNIT_SCROLL: {
            scrollType = Mouse.ScrollType.UNIT;
            break;
        }
        }

        // Process the event
        switch (event.getID()) {
        case MouseEvent.MOUSE_WHEEL: {
            if (dragDescendant == null) {
                // Determine the mouse owner
                Component mouseOwner;
                Component mouseCapturer = Mouse.getCapturer();
                if (mouseCapturer == null) {
                    mouseOwner = scene;
                } else {
                    mouseOwner = mouseCapturer;
                    Point2D.Float location = mouseOwner.mapPointFromAncestor(scene, x, y);
                    x = (int)location.x;
                    y = (int)location.y;
                }

                // Delegate the event to the owner
                mouseOwner.mouseWheel(scrollType, event.getScrollAmount(),
                                      event.getWheelRotation(), x, y);
            }
            break;
        }
        }
    }

    protected void processKeyEvent(KeyEvent event) {
        //super.processKeyEvent(event);//

        Scene scene = this.scene;

        // Set the keyboard modifier state
        int keyboardModifiers = 0;

        int modifiersEx = event.getModifiersEx();
        if ((modifiersEx & KeyEvent.SHIFT_DOWN_MASK) > 0) {
            keyboardModifiers |= Keyboard.Modifier.SHIFT.getMask();
        }

        if ((modifiersEx & KeyEvent.CTRL_DOWN_MASK) > 0) {
            keyboardModifiers |= Keyboard.Modifier.CTRL.getMask();
        }

        if ((modifiersEx & KeyEvent.ALT_DOWN_MASK) > 0) {
            keyboardModifiers |= Keyboard.Modifier.ALT.getMask();
        }

        if ((modifiersEx & KeyEvent.META_DOWN_MASK) > 0) {
            keyboardModifiers |= Keyboard.Modifier.META.getMask();
        }

        Keyboard.setModifiers(keyboardModifiers);


        if (dragDescendant == null) {
            int keyCode = event.getKeyCode();
            if (Keyboard.Modifier.Is(keyCode)){
                event.consume();
            }
            else {
                Component focusedComponent = Component.GetFocusedComponent();

                if (focusedComponent != null){
                    Keyboard.KeyLocation keyLocation = null;
                    switch (event.getKeyLocation()) {
                    case KeyEvent.KEY_LOCATION_STANDARD:
                        keyLocation = Keyboard.KeyLocation.STANDARD;
                        break;
                    case KeyEvent.KEY_LOCATION_LEFT:
                        keyLocation = Keyboard.KeyLocation.LEFT;
                        break;
                    case KeyEvent.KEY_LOCATION_RIGHT:
                        keyLocation = Keyboard.KeyLocation.RIGHT;
                        break;
                    case KeyEvent.KEY_LOCATION_NUMPAD:
                        keyLocation = Keyboard.KeyLocation.KEYPAD;
                        break;
                    default:
                        break;
                    }

                    switch (event.getID()) {
                    case KeyEvent.KEY_PRESSED:

                        if (focusedComponent.keyPressed(keyCode, keyLocation))
                            event.consume();
                        break;

                    case KeyEvent.KEY_RELEASED:

                        if (focusedComponent.keyReleased(keyCode, keyLocation))
                            event.consume();
                        break;

                    case KeyEvent.KEY_TYPED:

                        char keyChar = event.getKeyChar();
                        if (focusedComponent.keyTyped(keyChar))
                            event.consume();
                        break;

                    default:
                        break;
                    }
                }
            }
        }
        else {
            DragSource dragSource = dragDescendant.getDragSource();

            // If the user drop action changed, notify the drop descendant
            if (dropDescendant != null) {
                DropAction previousUserDropAction = userDropAction;
                userDropAction = Awtk.getUserDropAction(event);

                if (previousUserDropAction != userDropAction) {
                    DropTarget dropTarget = dropDescendant.getDropTarget();

                    Point2D.Float dropLocation = dropDescendant.mapPointFromAncestor(scene,
                                                                                     mouseLocation.x,
                                                                                     mouseLocation.y);
                    dropTarget.userDropActionChange(dropDescendant, dragManifest,
                                                    dragSource.getSupportedDropActions(),
                                                    (int)dropLocation.x, (int)dropLocation.y, 
                                                    userDropAction);
                }
            }
        }
    }
}
